﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.InteropServices;
using System.Collections;

namespace Liquifier
{

    public interface ILiquify
    {
        void OnLiquify(Liquifer liquifier);
    }

    public interface ISolidify
    {
        void OnSolidified(Liquifer solidifier);
    }

    public class LiquidInfo
    {
        public int ReferenceCount = 0;
        public int ObjectCount = 0;
        public int ValueCount = 0;
    }

    public class LiquificationContext
    {
        protected Dictionary<object, int> ReferenceItems = new Dictionary<object, int>();
        protected int counter = 0;

        public bool ContainsReference(object o)
        {
            return ReferenceItems.ContainsKey(o);
        }

        public int GetReference(object o)
        {
            return ReferenceItems[o];
        }

        public void AddReference(object o)
        {
            if (ReferenceItems.ContainsKey(o))
            {
                throw new Exception("Object Already Referenced: Object has already been encountered and added to the dictionary. There is no need to add it again.");
            }
                
            ReferenceItems.Add(o, counter);
            counter++;

            Statistics.ObjectCount++;
        }

        public LiquidInfo Statistics = new LiquidInfo();
    }

    public class SolidificationContext
    {
        public Dictionary<int, object> ReferenceItems = new Dictionary<int, object>();
    }

    [Flags]
    public enum TypeFlags
    {
        /// <summary>
        /// Do not serialise any special types (e.g. Delegates, Events) by default. Members with Serialise specified will still be processed.
        /// </summary>
        None = 0,

        /// <summary>
        /// Serialise all delegates
        /// </summary>
        DelegatesAndEvents = 1
    }

    [Flags]
    public enum Serialisation
    {
        /// <summary>
        /// Do not serialise any member of this type
        /// </summary>
        None = 0,
        /// <summary>
        /// Serialise all Public members of this type
        /// </summary>
        Public = 1,
        /// <summary>
        /// Serialise all Private members of this type
        /// </summary>
        Private = 2,
        /// <summary>
        /// Serialise members of this type so long as they have the Liquify attribute applied
        /// </summary>
        Explicit = 4
    }

    [Flags]
    public enum SerialisationOptions
    {
        /// <summary>
        /// Marks this property or field for serialisation
        /// </summary>
        Default = 0,

        /// <summary>
        /// If the field or property implements IList, an existing object will be updated with the contents from the serialized enumerable item
        /// </summary>
        UpdateExistingIList = 1,

        /// <summary>
        /// Values will be serialized and stored, but will not be restored to their original property or field - to use them implement ILiquifiable and
        /// access them via the LiquificationInfo parameter
        /// </summary>
        SerializeOnly = 2,

        /// <summary>
        /// Values will not be serialized
        /// </summary>
        NonSerialized = 4,

        /// <summary>
        /// When applied to a Member, it will add the specified member name to the IncludeMembers property of that class, so when that specific instance is
        /// serialized, the members will be included regardless of the original settings.
        /// </summary>
        IncludeMembers = 8
    }

    #region Attributes

    [System.AttributeUsage(AttributeTargets.All, AllowMultiple=true)]
    public class LiquifyAttribute : System.Attribute
    {
        public LiquifyAttribute()
        {
            this.SerializeAsType = null;
            this.Options = SerialisationOptions.Default;
        }
        public LiquifyAttribute(SerialisationOptions options)
        {
            this.SerializeAsType = null;
            this.Options = options;
        }

        public SerialisationOptions Options { get; set; }
        public Type SerializeAsType { get; set; }
    }

    public class LiquifiableAttribute : System.Attribute
    {
        public LiquifiableAttribute()
        {
            this.FieldFlags = Serialisation.Public | Serialisation.Private | Serialisation.Explicit;
            this.PropertyFlags = Serialisation.Explicit;
            this.SpecialTypeFlags = TypeFlags.None;
        }

        public LiquifiableAttribute(Serialisation FieldFlags, Serialisation PropertyFlags)
        {
            this.FieldFlags = FieldFlags;
            this.PropertyFlags = PropertyFlags;
            this.SpecialTypeFlags = TypeFlags.None;
        }

        public LiquifiableAttribute(Serialisation FieldFlags, Serialisation PropertyFlags, TypeFlags SpecialTypes)
        {
            this.FieldFlags = FieldFlags;
            this.PropertyFlags = PropertyFlags;
            this.SpecialTypeFlags = SpecialTypes;
        }

        /// <summary>
        /// Flags specifying which Fields to include
        /// </summary>
        public Serialisation FieldFlags { get; set; }

        /// <summary>
        /// Flags specifying which Properties to include
        /// </summary>
        public Serialisation PropertyFlags { get; set; }

        /// <summary>
        /// Flags specifying which special types, not normally serialised, will be included. Subject to the usual member requirements (accessibility, etc)
        /// </summary>
        public TypeFlags SpecialTypeFlags { get; set; }
        
        /// <summary>
        /// Any members specified here will be included regardless of the member filters or the liquify attribute applied.
        /// SerializeOnly will still be respected. (This feature still works but is no longer supported, or referenced in 
        /// the unit tests hence this property is hidden)
        /// </summary>
        internal  List<string> IncludeMembers { get; set; }

        public int GetNonCollidingHashCode()
        {
            int code = 0;
            code |= (byte)this.FieldFlags;
            code <<= 8;
            code |= (byte)this.PropertyFlags;
            code <<= 8;
            code |= (byte)this.SpecialTypeFlags;

            return code;
        }
    }

    #endregion

}
